package ldap

import (
	"crypto/tls"
	"errors"
	"fmt"
	"github.com/caddyserver/caddy/caddyhttp/httpserver"
	"github.com/coocood/freecache"
	"github.com/go-ldap/ldap/v3"
	"net"
	"net/http"
	"time"
)

var cache = freecache.NewCache(1024 * 1024 * 2)

type CaddyLdapHandler struct {
	Next    httpserver.Handler
	Configs []LDAPConfig
}

func (a *CaddyLdapHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
	host := r.Host
	username, password, ok := r.BasicAuth()

	if !ok {
		w.Header().Set("WWW-Authenticate", `Basic realm="User Login"`)
		w.WriteHeader(http.StatusUnauthorized)
		return http.StatusUnauthorized, nil
	}
	if len(a.Configs) == 0 {
		w.Header().Set("WWW-Authenticate", `Basic realm="User Login"`)
		w.WriteHeader(http.StatusInternalServerError)
		return http.StatusInternalServerError, fmt.Errorf("ldap没有配置")

	}
	for _, config := range a.Configs {
		err := cacheLdapAuth(&config, username, password, host)
		if err != nil {
			w.Header().Set("WWW-Authenticate", `Basic realm="User Login"`)
			w.WriteHeader(http.StatusUnauthorized)
			return http.StatusUnauthorized, err
		}
	}

	delete(r.Header, "Authorization")

	return a.Next.ServeHTTP(w, r)

}
func cacheLdapAuth(LDAPConfig *LDAPConfig, username, password, host string) error {
	if username == "" || password == "" {
		return fmt.Errorf("用户名或密码为空")
	}
	userKey := fmt.Sprintf("%s:%s", username, host)
	value, err := cache.Get([]byte(userKey))
	if err != nil {
		if !errors.Is(err, freecache.ErrNotFound) {
			return fmt.Errorf("得到缓存%s出现错误:%w", username, err)
		}
	}
	// 用户名和密码匹配不需要ldap认证
	if string(value) == password {
		fmt.Printf("%s 密码缓存匹配成功\n", username)
		return nil
	}

	err = ldapAuth(LDAPConfig, username, password)
	if err != nil {
		return err
	}

	err = cache.Set([]byte(userKey), []byte(password), 60*60*5)
	if err != nil {
		return fmt.Errorf("缓存用户名密码出错: %w", err)
	}
	return nil
}

func ldapAuth(LDAPConfig *LDAPConfig, username, password string) error {
	timeOut := ldap.DialWithDialer(&net.Dialer{Timeout: time.Second * 3})
	conn, err := ldap.DialURL(LDAPConfig.Addr, timeOut)
	if err != nil {
		return fmt.Errorf("连接ldap错误: %w", err)
	}

	defer conn.Close()

	conn.SetTimeout(time.Second * 3)

	if LDAPConfig.StartTLS {
		err = conn.StartTLS(&tls.Config{InsecureSkipVerify: true})
		if err != nil {
			return fmt.Errorf("ldap使用StartTLS错误: %w", err)
		}
	}

	err = conn.Bind(LDAPConfig.BindDn, LDAPConfig.BindPass)
	if err != nil {
		return err
	}

	searchRequest := ldap.NewSearchRequest(
		LDAPConfig.BaseDn, // The base dn to search
		ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
		fmt.Sprintf(LDAPConfig.AuthFilter, username), // The filter to apply
		[]string{}, // A list attributes to retrieve
		nil,
	)
	sr, err := conn.Search(searchRequest)
	if err != nil {
		return err
	}
	if len(sr.Entries) == 0 {
		return fmt.Errorf("cannot find such user")

	}
	if len(sr.Entries) > 1 {
		return fmt.Errorf("multi users in search")

	}
	// Bind as the user to verify their password
	err = conn.Bind(sr.Entries[0].DN, password)
	if err != nil {
		return err
	}

	return nil

}
